#include "../utils.hpp"
#include <learn/mesh/mesh.h>

using namespace xi::mesh;

// 当前路径
#define SOURCE_PATH ROOT_SOURCE_PATH "/src/core/planet/"

unsigned int amount = 20000;
Mesh_Data planetMesh, rockMesh;
unsigned int planetVAO, planetVBO, planetEBO;
unsigned int rockVAO, rockVBO, rockEBO;
unsigned int planetTexture, rockTexture, specularTexture;

std::shared_ptr<Shader> basicShader, instanceShader;

void initMesh(Mesh_Data &mesh, unsigned int &VAO, unsigned int &VBO, unsigned int &EBO)
{
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

    auto n = mesh->vertices.size();
    glBufferData(GL_ARRAY_BUFFER, n * (sizeof(Vec3f) * 2 + sizeof(Vec2f)), NULL, GL_DYNAMIC_DRAW);

    // 载入顶点和索引数据
    glBufferSubData(GL_ARRAY_BUFFER, 0, n * sizeof(Vec3f), mesh->vertices.data());
    glBufferSubData(GL_ARRAY_BUFFER, n * sizeof(Vec3f), n * sizeof(Vec3f), mesh->normals.data());
    glBufferSubData(GL_ARRAY_BUFFER, 2 * n * sizeof(Vec3f), n * sizeof(Vec2f), mesh->texcoords.data());
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh->indices.size() * sizeof(unsigned int), mesh->indices.data(),
                 GL_DYNAMIC_DRAW);

    // vertex Positions
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vec3f), (void *)0);

    // vertex normals
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vec3f), (void *)(n * sizeof(Vec3f)));

    // vertex texture coords
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vec2f), (void *)(2 * n * sizeof(Vec3f)));
}

void renderBasicMesh(Mesh_Data &mesh, unsigned int VAO, unsigned int EBO, unsigned int texture)
{
    basicShader->enable();
    basicShader->set_value("projection", camera->projection());
    basicShader->set_value("view", camera->view());
    basicShader->set_value("model", scale(Vec3f{8.0f}));
    basicShader->set_value("viewPos", camera->position());

    glBindVertexArray(VAO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glDrawElements(GL_TRIANGLES, mesh->indices.size(), GL_UNSIGNED_INT, 0);
}

void renderInstanceMesh(Mesh_Data &mesh, unsigned int VAO, unsigned int EBO, unsigned int texture)
{
    instanceShader->enable();
    instanceShader->set_value("fix", Mat4x4<float>::identity());
    instanceShader->set_value("view", camera->view());
    instanceShader->set_value("viewPos", camera->position());

    glBindVertexArray(VAO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glDrawElementsInstanced(GL_TRIANGLES, mesh->indices.size(), GL_UNSIGNED_INT, 0, amount);
}

void init()
{
    planetMesh.read(SOURCE_PATH "model/planet.obj", 1.0f);
    rockMesh.read(SOURCE_PATH "model/rock.obj", 1.0f);

    basicShader = std::make_shared<Shader>(SOURCE_PATH "test.vert", SOURCE_PATH "test.frag", nullptr);
    instanceShader = std::make_shared<Shader>(SOURCE_PATH "instance.vert", SOURCE_PATH "test.frag", nullptr);

    // 启用深度测试
    glEnable(GL_DEPTH_TEST);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    // 创建顶点数组对象和缓冲区对象
    initMesh(planetMesh, planetVAO, planetVBO, planetEBO);
    initMesh(rockMesh, rockVAO, rockVBO, rockEBO);

    // 配置材质
    glGenTextures(1, &planetTexture);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, planetTexture);
    loadTexture(planetMesh->material->map_Kd.c_str());

    glGenTextures(1, &rockTexture);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, rockTexture);
    loadTexture(rockMesh->material->map_Kd.c_str());

    glGenTextures(1, &specularTexture);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, specularTexture);
    loadTexture(SOURCE_PATH "model/specular.png");

    // 载入光照信息
    basicShader->enable();
    basicShader->set_value("projection", camera->projection());

    // 使用平行光源
    basicShader->set_value("light.lightDir", Vec3f(-1.0f, -1.0f, -1.0f));
    basicShader->set_value("light.ambient", planetMesh->material->Ka);
    basicShader->set_value("light.diffuse", planetMesh->material->Kd);
    basicShader->set_value("light.specular", planetMesh->material->Ks);

    basicShader->set_value("material.diffuse", 0);
    basicShader->set_value("material.specular", 1);

    instanceShader->enable();
    instanceShader->set_value("projection", camera->projection());

    instanceShader->set_value("light.lightDir", Vec3f(-1.0f, -1.0f, -1.0f));
    instanceShader->set_value("light.ambient", rockMesh->material->Ka);
    instanceShader->set_value("light.diffuse", rockMesh->material->Kd);
    instanceShader->set_value("light.specular", rockMesh->material->Ks);

    instanceShader->set_value("material.diffuse", 0);
    instanceShader->set_value("material.specular", 1);

    // 生成变换矩阵
    srand(glfwGetTime());
    Mat4x4<float> *modelMatrices = new Mat4x4<float>[amount];
    float radius = 50.0f;
    float offset = 10.0f;
    for (unsigned int i = 0; i < amount; i++)
    {
        auto model = Mat4x4<float>::identity();

        // 1. 缩放：在 0.05 和 0.25f 之间缩放
        float scale = (rand() % 20) / 100.0f + 0.05;
        model = geom::scale(Vec3f(scale)) * model;

        // 2. 旋转：绕着一个（半）随机选择的旋转轴向量进行随机的旋转
        float rotAngle = (rand() % 360);
        model = geom::rotate(Vec3f(0.4f, 0.6f, 0.8f), rotAngle) * model;

        // 3. 位移：分布在半径为 'radius' 的圆形上，偏移的范围是 [-offset, offset]
        float angle = (float)i / (float)amount * 360.0f;
        float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float x = sin(angle) * radius + displacement;
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float y = displacement * 0.4f; // 让行星带的高度比 x 和 z 的宽度要小
        displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
        float z = cos(angle) * radius + displacement;
        model = geom::translate(Vec3f(x, y, z)) * model;

        // 4. 添加到矩阵的数组中（注意自定义矩阵要转置传入）
        modelMatrices[i] = model.transposed();
    }

    // 绑定顶点数组，绑定实例顶点缓冲
    unsigned int Ins;
    glGenBuffers(1, &Ins);
    glBindBuffer(GL_ARRAY_BUFFER, Ins);

    // 传入实例矩阵数据
    glBufferData(GL_ARRAY_BUFFER, amount * sizeof(Mat4x4<float>), modelMatrices, GL_STATIC_DRAW);

    // 顶点属性
    glEnableVertexAttribArray(3);
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(Vec4f), (void *)0);

    glEnableVertexAttribArray(4);
    glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(Vec4f), (void *)(sizeof(Vec4f)));

    glEnableVertexAttribArray(5);
    glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(Vec4f), (void *)(2 * sizeof(Vec4f)));

    glEnableVertexAttribArray(6);
    glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(Vec4f), (void *)(3 * sizeof(Vec4f)));

    // 指定 3 4 5 6 号属性每渲染 1 个实例就更新数据
    glVertexAttribDivisor(3, 1);
    glVertexAttribDivisor(4, 1);
    glVertexAttribDivisor(5, 1);
    glVertexAttribDivisor(6, 1);
}

void render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    renderBasicMesh(planetMesh, planetVAO, planetEBO, planetTexture);
    renderInstanceMesh(rockMesh, rockVAO, rockEBO, rockTexture);
}

int main()
{
    GLFWwindow *window = initProgram();
    if (window == nullptr)
        return -1;

    // 创建顶点数据
    init();

    // 开启消息循环
    while (!glfwWindowShouldClose(window))
    {
        // 渲染指令
        render();

        // 交换缓冲，处理并调用事件
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 销毁资源
    glfwTerminate();

    return 0;
}